class 继承箭头函数采坑

抛出问题

下述代码中,Child 类继承自 Parent 类,我们希望 Child 类中 af 方法能够覆盖 Parent 类中箭头函数 af 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 父类
class Parent {
af = () => {
console.log('parent af')
}
}
// 子类
class Child extends Parent {
af () {
console.log('child af')
}
}
const child = new Child()
child.af()

上述代码调用 child.af() 后会打印出 ‘parent af’ 而非期望的 ‘child af’。为什么?

ES6 class -> ES5 function

1
2
3
4
5
class Parent() {
f() {console.log('parent f')}
af = () => {console.log('parent af')}
}

上述代码通过 babel 转义后变为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var Parent = function () {
function Parent() {
_classCallCheck(this, Parent);
// 将 af 绑定到 Parent 的 this
this.af = function () {
console.log('parent af');
};
}
// 将 f 绑定到 Parent 的 prototype
_createClass(Parent, [{
key: 'f',
value: function f() {console.log('parent f')}
}]);
return Parent;
}()

从上述代码可以看出,f 通过 _createClass 绑定到 Parentprototype 上,而箭头函数 af 则是直接绑定到 Parentthis 上,因此上述代码等价于:

1
2
3
4
5
6
7
8
9
var Parent = function() {
this.af = function () {
console.log('parent af');
};
}
Parent.prototype.f = function() {
console.log('parent f')
}

寄生组合式继承

回顾下经典的寄生组合式继承,通过 inherit 继承的类其原型链为:

child._proto_ => Child.prototype
Child.prototype._proto_ => F.prototype = Parent.prototype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 构建对象
const createObj = obj => {
function F(){}
F.prototype = obj
return new F()
}
// 继承方法
const inherit = (Child, Parent) => {
const p = createObj(Parent.prototype)
p.constructor = Child
Child.prototype = p
}
class Parent {}
class Child {}
inherit(Child, Parent)
var child = new Child()

ES6 class extends 继承

事实上,ES6 中的 class extends 继承方式原理参照的就是寄生组合式继承,将文章最开始的 Child 类转义成 ES5 就变成了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var Child = function (Parent) {
// 继承 Parent
_inherits(Child, Parent);
function Child() {
_classCallCheck(this, Child);
return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
}
// 将 af 绑定到 Child 的 prototype
_createClass(Child, [{
key: 'af',
value: function af() {}
}]);
return Child;
}(Parent);

上述代码等价于:

1
2
3
4
5
var Child = function(){}
Child.prototype.af = function() {console.log('child af')}
inherit(Child, Parent)

根据继承原理,Child 会继承 Parentthis 属性,因此实例 child 内部含有从
Parent 中继承下来的 this.af 方法,会屏蔽 Child.prototype.af 方法,所以文章最开头的代码 child.af() 会打印出 parent af 而不是 child af

解决方案

如果父类中定义了箭头函数属性,子类中的同名函数欲覆盖它,则子类中同名函数也应该定义为箭头函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ES6
class Child extends Parent {
af = () => {
console.log('child af')
}
}
// ES5
var Child = function (_Parent) {
_inherits(Child, _Parent);
function Child() {
var _ref;
var _temp, _this, _ret;
_classCallCheck(this, Child);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
// 覆盖父类的方法
return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Child.__proto__ || Object.getPrototypeOf(Child)).call.apply(_ref, [this].concat(args))), _this), _this.af = function () {
console.log('child af');
}, _temp), _possibleConstructorReturn(_this, _ret);
}
return Child;
}(Parent);

另外由于箭头函数 af 是直接绑定在父类函数上的,因此子类中无法通过 super.af() 的方式调用他,因为 super === Child.prototype